const fsPromises = require('fs').promises
const path = require('path')
const Room = require('../room.js')

const saveFileName = path.join(__dirname, 'save.json')
const colors = ['red', 'lime', 'blue', 'orange', 'fuchsia', 'yellow', 'aqua']

let table
const players = [null, null, null, null, null, null, null]
let playerCount

function broadcast(msg) {
  for (let i = 0; i < players.length; i++) {
    if (players[i]) {
      room.sendTo(i, msg)
    }
  }
}

function unlinkObj(obj) {
  obj.prev.next = obj.next
  obj.next.prev = obj.prev
}

function insertBefore(newObj, obj) {
  newObj.prev = obj.prev
  newObj.next = obj
  obj.prev.next = newObj
  obj.prev = newObj
}

function insertAfter(newObj, obj) {
  newObj.prev = obj
  newObj.next = obj.next
  obj.next.prev = newObj
  obj.next = newObj
}

function getObjList() {
  const list = []
  let next = table.bottom.next
  while (next != table.top) {
    const obj = { ...next }
    delete obj.prev
    delete obj.next
    list.push(obj)
    next = next.next
  }
  return list
}

const room = new Room({
  port: 7788,
  maxPlayers: players.length,
  minPlayers: 1
}, async ps => {
  let save
  let nextID = 0

  try {
    save = JSON.parse(await fsPromises.readFile(saveFileName, 'utf8'))
  } catch (e) {
    console.error(e)
    return
  }

  for (const p of ps) {
    players[p] = { color: colors[p] }
  }
  playerCount = ps.length

  for (let i = 0; i < players.length; i++) {
    if (players[i]) {
      room.sendTo(i, { type: 'init', players, me: i, ...save })
    }
  }

  table = {
    width: save.width,
    height: save.height,
    bottom: {},
    top: {},
    index: {}
  }
  if (save.objects.length) {
    table.bottom.next = save.objects[0]
    table.top.prev = save.objects[save.objects.length - 1]
    for (let i = 0; i < save.objects.length; i++) {
      const obj = save.objects[i]
      table.index[obj.id] = obj
      obj.prev = save.objects[i - 1]
      obj.next = save.objects[i + 1]
    }
    table.bottom.next.prev = table.bottom
    table.top.prev.next = table.top
  } else {
    table.bottom.next = table.top
    table.top.prev = table.bottom
  }

  while (1) {
    let msg
    try {
      msg = await room.recv()
    } catch (e) {
      players[e] = null
      playerCount--
      if (playerCount <= 0) {
        break
      }
      broadcast({ type: 'leave', player: e })
      continue
    }
    let { message } = msg
    message.player = msg.player
    let obj, ind, list
    switch (message.type) {
      case 'select':
        obj = table.index[message.objectId]
        if (!obj || obj.fixed) {
          continue
        }
        unlinkObj(obj)
        insertBefore(obj, table.top)
        break
      case 'selectAll':
        list = []
        for (const id of message.list) {
          obj = table.index[id]
          if (!obj || obj.fixed) {
            continue
          }
          unlinkObj(obj)
          insertBefore(obj, table.top)
          list.push(id)
        }
        message.list = list
        break
      case 'move':
        obj = table.index[message.objectId]
        if (!obj || obj.fixed) {
          continue
        }
        obj.left = message.toX
        obj.top = message.toY
        break
      case 'moveAll':
        list = []
        for (const id of message.list) {
          obj = table.index[id]
          if (!obj || obj.fixed) {
            continue
          }
          obj.left += message.deltaX
          obj.top += message.deltaY
          list.push(id)
        }
        message.list = list
        break
      case 'shuffle':
        list = []
        while (message.list.length > 0) {
          ind = Math.floor(Math.random() * message.list.length)
          const id = message.list[ind]
          message.list.splice(ind, 1)
          obj = table.index[id]
          if (!obj || obj.fixed) {
            continue
          }
          unlinkObj(obj)
          insertBefore(obj, table.top)
          list.push(id)
        }
        message.list = list
        break
      case 'turn':
        obj = table.index[message.objectId]
        if (!obj || !obj.backsrc) {
          continue
        }
        obj.turned = !obj.turned
        break
      case 'rotate':
        obj = table.index[message.objectId]
        if (!obj) {
          continue
        }
        if (obj.rotate == null) {
          obj.rotate = 0
        }
        obj.rotate += message.deg
        break
      case 'fix':
        obj = table.index[message.objectId]
        if (!obj || !!obj.fixed == message.fixed) {
          continue
        }
        obj.fixed = message.fixed
        break
      case 'bottom':
        obj = table.index[message.objectId]
        if (!obj) {
          continue
        }
        unlinkObj(obj)
        insertAfter(obj, table.bottom)
        break
      case 'clone':
        obj = table.index[message.objectId]
        if (!obj) {
          continue
        }
        if (nextID <= 0) {
          for (const id in table.index) {
            if (id > nextID) {
              nextID = id
            }
          }
          nextID++
        }
        const nobj = { ...obj }
        nobj.left += 5
        nobj.top += 5
        nobj.id = nextID
        nextID++
        insertBefore(nobj, table.top)
        table.index[nobj.id] = nobj
        message = {
          type: 'add',
          object: nobj,
          player: msg.player
        }
        break
      case 'delete':
        obj = table.index[message.objectId]
        if (!obj) {
          continue
        }
        unlinkObj(obj)
        delete table.index[message.objectId]
        break
    }
    broadcast(message)
  }

  save.objects = getObjList()
  try {
    await fsPromises.writeFile(saveFileName, JSON.stringify(save))
  } catch (e) {
    console.error(e)
  }
}, p => {
  const info = { color: colors[p] }
  broadcast({ type: 'join', playerId: p, playerInfo: info })
  players[p] = info
  playerCount++
  room.sendTo(p, {
    type: 'init',
    players,
    me: p,
    width: table.width,
    height: table.height,
    objects: getObjList()
  })
})
